package com.ruoyi.business.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ruoyi.bizsys.domain.*;
import com.ruoyi.bizsys.enums.AwardEventEnum;
import com.ruoyi.bizsys.mapper.ExtIdentityResultMapper;
import com.ruoyi.bizsys.mapper.RealNameAuthMapper;
import com.ruoyi.bizsys.service.IBankAccountService;
import com.ruoyi.bizsys.service.IExtIdentityResultService;
import com.ruoyi.bizsys.service.IExtReqLogService;
import com.ruoyi.bizsys.service.IRealNameAuthLogService;
import com.ruoyi.bizsys.service.impl.RealNameAuthLogServiceImpl;
import com.ruoyi.bizsys.utils.UserCacheService;
import com.ruoyi.business.common.AsyncService;
import com.ruoyi.business.common.constant.KycApiEnum;
import com.ruoyi.business.domain.BVNInfo;
import com.ruoyi.business.mapper.BusinessMapper;
import com.ruoyi.business.mapper.ExtIdentityMapper;
import com.ruoyi.business.service.*;
import com.ruoyi.common.utils.business.CheckAccountNameUtil;
import com.ruoyi.bizsys.utils.BizConstants;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.enums.RspEnmu;
import com.ruoyi.common.enums.UserTagEnum;
import com.ruoyi.common.exception.BusinessException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.DesensitizeUtils;
import com.ruoyi.common.utils.Func;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.reflect.ReflectUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.extdata.common.config.OkraConfig;
import com.ruoyi.extdata.service.ICrsService;
import com.ruoyi.extdata.service.IExternalApiService;
import com.ruoyi.extdata.service.IOnePipeService;
import com.ruoyi.sms.domain.SmsSendData;
import com.ruoyi.sms.domain.SmsSendReq;
import com.ruoyi.sms.service.ISmsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * cyh
 */
@Slf4j
@Service
public class RealNameServiceImpl extends BaseService implements IRealNameService {

    @Autowired
    private IAppService appService;

    @Autowired
    private AsyncService asyncService;

    @Autowired
    private OkraConfig okraConfig;

    @Autowired
    private IPayTransService payTransService;

    @Autowired
    private BusinessMapper businessMapper;

    @Autowired
    private IAwardDistributionService awardDistributionService;

    @Autowired
    private RealNameAuthMapper realNameAuthMapper;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private ISmsService smsService;

    @Autowired
    private IOnePipeService onePipeService;

    @Autowired
    private IBankListService bankListService;

    @Autowired
    private IRealNameAuthLogService realNameAuthLogService;

    @Autowired
    private IExtIdentityResultService extIdentityResultService;

    @Autowired
    private IExternalApiService externalApiService;

    @Autowired
    private ICrsService crsService;

    @Autowired
    private IBankAccountService bankAccountService;

    @Autowired
    private UserCacheService userCacheService;

    @Autowired
    private IExtReqLogService extReqLogService;

    @Autowired
    private ExtIdentityResultMapper extIdentityResultMapper;

    private ThreadLocal<String> kycExqApiThreadLocal = new ThreadLocal<>();

    @Override
    public Object preAuth(BVNInfo bvnInfo, JSONObject deviceInfo) {
        log.info("preAuth,入参 BVNInfo：{},JSONObject:{}", bvnInfo, deviceInfo);
        String userId = getUserId();
        //用户失败次数预校验
        int failCount = realNameFailPreCheck(userId);
        realNameAuthIsEmpty(bvnInfo.getAccountNumber(), bvnInfo.getBvnAccount(), userId);
        //今日次数预校验
        long applicationCount = realNamePreRiskCheck(userId);

        RealNameAuthLog realNameAuthLog = new RealNameAuthLog();
        realNameAuthLog.setState("1");
        try {
            //bvn格式校验
            bvnFormatCheck(bvnInfo.getBvnAccount());
            //保存AppDeviceInfo
            if (deviceInfo != null && StringUtils.isNotBlank(deviceInfo.toString())) {
                appService.saveAppDeviceInfoBoard(userId, getCustNo(userId), BizConstants.ACTION_TYPE_REAL_NAME, "", deviceInfo);
            }

            //校验accountNUmber
            if (!Func.hasEmpty(bvnInfo.getAccountNumber(), bvnInfo.getBankCode())) {
                payTransService.checkAccountNumber(bvnInfo.getAccountNumber(), bvnInfo.getBankCode());
            }

            //三方接口校验
            ExtIdentityResult extIdentityResult = extApiVerification(bvnInfo);
            identityResultCheck(bvnInfo, extIdentityResult);

            // 校验生日
            dobVerify(bvnInfo.getBirthDate(), extIdentityResult.getDob(), realNameAuthLog);

            //注册手机号与BVN手机号不一致
            JSONObject questionJson = verifyIdentity(extIdentityResult);
            if (Func.isNotEmpty(questionJson.get("unmatch"))) {
                asyncService.apdUserTag(AppUserMark.builder().userId(userId).build(), UserTagEnum.IDENTITY_VERIFIED_FAILED_BVN_PHONE);
                return questionJson;
            }
            asyncService.apdUserTag(AppUserMark.builder().userId(userId).build(), UserTagEnum.IDENTITY_VERIFIED_FAILED_NOT_ERR);
            //若不满足风控需求 仍然发短信
            if (!realNameRiskCheck(bvnInfo.getFingerprint())) {
                return sendSms(null);
            }

        } catch (Exception e) {
            realNameAuthLog.setErrorMsg(e.getMessage());

            String info1 = "Information does not match, please check your information, ";
            String info2 = "Information does not match, please check your information, ";
            if (e instanceof BusinessException) {
                Integer code = ((BusinessException) e).getCode();
                log.error("实名认证异常：{}", ((BusinessException) e).getDetails());

                //接口异常 不计入实名次数
                if (RspEnmu.EXT_API_EXCEPTION.getCode().equals(code)) {
                    realNameAuthLog.setState(BizConstants.REAL_NAME_LOG_SKIP);
                    //网络异常标签
                    UserTagEnum userTagEnum = UserTagEnum.exceptionMapper(e.getMessage());
                    userTagEnum = Optional.ofNullable(userTagEnum).orElse(UserTagEnum.IDENTITY_VERIFIED_FAILED_NET_BUSY);
                    asyncService.apdUserTag(AppUserMark.builder().userId(userId).build(), userTagEnum);
                    throw new BusinessException(RspEnmu.EXT_API_EXCEPTION.getMessage());
                }

                //实名失败标签
                UserTagEnum userTagEnum = UserTagEnum.exceptionMapper(e.getMessage());
                userTagEnum = Optional.ofNullable(userTagEnum).orElse(UserTagEnum.getDiy("Identity verification failed", e.getMessage()));
                asyncService.apdUserTag(AppUserMark.builder().userId(userId).build(), userTagEnum);
                //实名失败
                if (RspEnmu.REAL_NAME_CHECK_FAILED.getCode().equals(code)) {
                    realNameAuthLog.setState(BizConstants.REAL_NAME_LOG_FAIL);
                    int n = 7;
                    if (failCount > 0) {
                        n = 30;
                    }
                    //throw new BusinessException("You can go to the bank to update your information and try again in " + n + " days", RspEnmu.REAL_NAME_CHECK_FAILED_JUMP_PAGE.getRetCode());
                    throw new BusinessException("Please contact the bank to update your information and try again in " + n + " days.", RspEnmu.REAL_NAME_CHECK_FAILED_JUMP_PAGE.getCode());
                }
                //BVN_Account不匹配
                if (RspEnmu.BVN_NOT_EQUALS_ACCOUNT_NUMBER.getCode().equals(code)) {
                    info1 = "BVN does not match, please check your information, ";
                    info2 = "BVN does not match, please check your information, ";
                }
                //验证码当天超次数
                if (RspEnmu.SMS_CODE_COUNT_LIMIT.getCode().equals(code)) {
                    throw new BusinessException(RspEnmu.SMS_CODE_COUNT_LIMIT.getMessage());
                }
            }

            log.error("实名认证异常：", e);

            //当前第几次
            applicationCount = applicationCount + 1;
            if (applicationCount == 4) {
                throw new BusinessException(info1 + "please try again 60 minutes later. ");
            }
            if (applicationCount == 5) {
                throw new BusinessException(info1 + "please try again 6 hours later. ");
            }
            if (applicationCount == 6) {
                throw new BusinessException(info1 + "please try again tomorrow. ");
            }
            long maxCount = 4;
            throw new BusinessException(info2 + "you still have " + (maxCount - applicationCount) + " chance. ");
        } finally {
            //保存提交信息至实名日志
            saveRealNameAuthLog(bvnInfo, realNameAuthLog, kycExqApiThreadLocal);
        }
        processRealNameAuth(false);
        return 200;
    }


    public static void bvnFormatCheck(String bvn) {
        //检查BVN是否为11位长度且开头为22
        if (bvn.length() != 11 || !bvn.startsWith("22")) {
            throw new BusinessException(RspEnmu.REAL_NAME_BVN_FAIL);
        }
    }


    @Override
    public boolean realNameRiskCheck(String fingerprint) {
        RealNameAuthLog realNameAuthLogQ = new RealNameAuthLog();
        realNameAuthLogQ.setUserId(getUserId());
        List<RealNameAuthLog> realNameAuthLogs = realNameAuthLogService.selectRealNameAuthLogList(realNameAuthLogQ);
        if (realNameAuthLogs.size() == 0) {
            return true;
        }
        RealNameAuthLog realNameAuthLog = realNameAuthLogs.get(realNameAuthLogs.size() - 1);
        long time = System.currentTimeMillis() - realNameAuthLog.getCreateTime().getTime();
        long day = time / (24 * 60 * 60 * 1000);
        //当前设备信息与首次实名时的设备信息是否一致 & 提交实名信息的时间是否大于首次提交实名信息的24小时
        return fingerprint.equals(realNameAuthLog.getFingerprint()) && day < 1;
    }


    @Override
    public JSONObject sendSms(String phone) {
        SmsSendReq smsReq = new SmsSendReq();
        smsReq.setSmsType(BizConstants.SMS_TYPE_REAL_NAME_AUTH);
        smsReq.setCountryCode(BizConstants.DEFAULT_COUNTRY_CODE);
        smsReq.setPhoneNumber(Func.isEmpty(phone) ? getAppUser().getPhone() : phone);
        SmsSendData smsData = smsService.sendVerifyCode(smsReq, false);
        JSONObject sms = new JSONObject();
        sms.put("uuid", smsData.getUuid());
        return sms;
    }

    @Override
    public void processRealNameAuth(Boolean isOldVersion) {
        String userId = getUserId();
        RealNameAuthLogServiceImpl realNameAuthLogService = SpringUtils.getBean(RealNameAuthLogServiceImpl.class);
        RealNameAuthLog realNameAuthLogQ = new RealNameAuthLog();
        realNameAuthLogQ.setUserId(userId);
        realNameAuthLogQ.setState(BizConstants.REAL_NAME_LOG_NORMAL);
        List<RealNameAuthLog> realNameAuthLogs = realNameAuthLogService.selectRealNameAuthLogList(realNameAuthLogQ);
        if (realNameAuthLogs.size() == 0) {
            throw new BusinessException(RspEnmu.REAL_NAME_CHECK_FAILED);
        }
        RealNameAuthLog realNameAuthLog = realNameAuthLogs.get(0);
        //是否已实名 bvn accountNumber userId
//        realNameAuthIsEmpty(realNameAuthLog.getAccountNumber(), realNameAuthLog.getBvnAccount(), userId);

        String phone = redisCache.getCacheObject(BizConstants.REAL_NAME_PHONE + realNameAuthLog.getCustNo());
        if (Func.isEmpty(phone)) {
            throw new BusinessException(RspEnmu.REAL_NAME_CHECK_FAILED);
        }

        //保存实名信息
        RealNameAuth realNameAuth = new RealNameAuth();
        BeanUtils.copyProperties(realNameAuthLog, realNameAuth);
        realNameAuth.setState(BizConstants.BIZ_STATUS_1);
        realNameAuth.setPhoneNumber(phone);

        Map<String, Integer> ageDetail = DateUtils.getAgeDetailByddmmyyyy(realNameAuthLog.getBirthDate());
        realNameAuth.setAge(Long.valueOf(ageDetail.get("year")));

        //数据表空值有唯一索引 写入null
        if (Func.isEmpty(realNameAuth.getAccountNumber())) {
            realNameAuth.setAccountNumber(null);
        }
        //是否已实名
        RealNameAuth bvnAccount = realNameAuthMapper.checkRealName(realNameAuth.getBvnAccount(), realNameAuth.getAccountNumber());
        if (bvnAccount != null) {
            throw new BusinessException(RspEnmu.REAL_NAME_ALREADY_EXISTS);
        }
        //实名成功
        if (realNameAuthService.save(realNameAuth)) {
            //删除用户缓存信息
            userCacheService.removeUserCache(realNameAuth.getUserId());
            //用户标签
            //realNameSuccessTag(realNameAuth);
            asyncService.apdUserTag(AppUserMark.builder().userId(realNameAuth.getUserId())
                    .lastName(realNameAuth.getLastName()).birthday(realNameAuth.getBirthDate())
                    .bvnPhone(realNameAuth.getPhoneNumber()).gender(realNameAuth.getGender())
                    .build(), UserTagEnum.IDENTITY_VERIFIED_SUCCESS);
            //保存Account账户信息
            if (isOldVersion) {
                bankAccountService.saveByRealNameAuth(realNameAuth);
            } else {
                asyncService.appendAccount(userId);
            }

            //奖励分销活动
            awardDistributionService.distributionAwardTrigger(AwardEventEnum.REAL_NAME_SUCCESS, realNameAuth.getUserId(), null);
        }
    }

//    private void realNameSuccessTag(RealNameAuth realNameAuth) {
//        //用户标签-实名成功
//        AppUserMark appUserMark = AppUserMark.builder().userId(realNameAuth.getUserId())
//                .lastName(realNameAuth.getLastName()).birthday(realNameAuth.getBirthDate())
//                .bvnPhone(realNameAuth.getPhoneNumber()).gender(realNameAuth.getGender())
//                .build();
//        asyncService.apdUserTag(appUserMark, UserTagEnum.IDENTITY_VERIFIED_SUCCESS);
//    }

    @Override
    public void saveRealNameAuthLog(BVNInfo bvnInfo, RealNameAuthLog realNameAuth, ThreadLocal<String> threadLocal) {

        String userId = getUserId();
        realNameAuth.setUserId(userId);
        realNameAuth.setCustNo(getCustNo(userId));
        realNameAuth.setFirstName(bvnInfo.getFirstName());
        realNameAuth.setMiddleName(bvnInfo.getMiddleName());
        realNameAuth.setLastName(bvnInfo.getLastName());
        realNameAuth.setBirthDate(bvnInfo.getBirthDate());
        realNameAuth.setGender(bvnInfo.getGender());
        realNameAuth.setAccountNumber(bvnInfo.getAccountNumber());
        realNameAuth.setPhoneNumber(bvnInfo.getPhoneNumber());
        realNameAuth.setFingerprint(bvnInfo.getFingerprint());
        realNameAuth.setBankCode(bvnInfo.getBankCode());
        realNameAuth.setBvnAccount(bvnInfo.getBvnAccount());
        realNameAuth.setFingerprint(bvnInfo.getFingerprint());
        realNameAuth.setExqApi(threadLocal.get());
        Map<String, Integer> ageDetail = DateUtils.getAgeDetailByddmmyyyy(bvnInfo.getBirthDate());
        realNameAuth.setAge(Long.valueOf(ageDetail.get("year")));
        realNameAuthLogService.insertRealNameAuthLog(realNameAuth);
        threadLocal.remove();
    }

    //是否已实名
    private void realNameAuthIsEmpty(String accountNumber, String bvn, String userId) {
//        if (extIdentityMapper.realNameAuthIsEmpty(userId, bvn, accountNumber) > 0) {
//            throw new BusinessException(RspEnmu.REAL_NAME_AUTH_USED);
//        }
        RealNameAuth bvnAccount = realNameAuthMapper.checkRealName(bvn, accountNumber);
        if (bvnAccount != null) {
            throw new BusinessException(RspEnmu.REAL_NAME_ALREADY_EXISTS);
        }
    }

    /**
     * @param userId
     * @return 当日已申请次数
     */
    private long realNamePreRiskCheck(String userId) {

        //实名提交4次之后等待60分钟后才能再次提交，60分钟后再提交一次后等6小时后才能提交，6小时后再次提交则需要等待当天23:59:59后才可以重新提交
        //查询当日总数,并且返回最新的时间
        Map resMap = businessMapper.getRealNameLogCountOfDayAndLatestTime(userId);
        long count = (long) resMap.get("count");
        if (count > 5) {
            //隔天再试
            throw new BusinessException(RspEnmu.SUBMIT_TOO_FREQUENTLY_TOMORROW);
        }
        if (count > 3) {
            Date date = (Date) resMap.get("createTime");
            String dateBetween = DateUtils.getDateBetween(5, new Date(), date);
            Integer hour = Integer.valueOf(dateBetween);
            //实名提交4次之后等待60分钟后才能再次提交
            if (count == 4 && hour < 1) {
                throw new BusinessException(RspEnmu.SUBMIT_TOO_FREQUENTLY_60MINUTES);
            }
            //6小时后才能提交
            if (count == 5 && hour < 6) {
                throw new BusinessException(RspEnmu.SUBMIT_TOO_FREQUENTLY_6HOUR);
            }
        }
        return count;
    }

    public static void verifyName(String basisName, String accountName) {
        if (!CheckAccountNameUtil.verifyName(basisName, accountName)) {
            throw new BusinessException(RspEnmu.REAL_NAME_CHECK_FAILED_BY_NAME);
        }
    }

    private void identityResultCheck(BVNInfo bvnInfo, ExtIdentityResult extIdentifyResult) {

        if (extIdentifyResult == null) {
            log.info("实名数据为空");
            throw new BusinessException(RspEnmu.CYC_INFORMATION_NOT_FOUND);
        }
        String apiName = extIdentifyResult.getApiName();
        //实名bvn与用户bvn是否一致
        if (StringUtils.equalsAny(apiName, "Lookup-Account-Mid", "NUBAN-Identity-Verification", "NUBAN-Identity-Verification-back") && !extIdentifyResult.getBvn().equals(bvnInfo.getBvnAccount())) {
            throw new BusinessException(RspEnmu.REAL_NAME_CHECK_FAILED_BY_BVN_2);
        }

        //Func.hasEmpty(extIdentifyResult.getPhones(), extIdentifyResult.getDob()) 更新 -> 只校验手机号是否为空
        if (Func.isEmpty(extIdentifyResult.getPhones())) {
            throw new BusinessException(RspEnmu.REAL_NAME_CHECK_FAILED_BIRTH_PHONE);
        }

        //姓名是否匹配
        String middleName = StringUtils.isNotBlank(bvnInfo.getMiddleName()) ? bvnInfo.getMiddleName().trim() : "";
        String basisName = bvnInfo.getFirstName().trim() + " " + middleName + " " + bvnInfo.getLastName().trim();
        verifyName(basisName, extIdentifyResult.getFullname());
    }

    @Override
    public void dobVerify(String birthDate, String bvnDob, RealNameAuthLog realNameAuthLog) {
        if (Func.isEmpty(bvnDob)) {
            realNameAuthLog.setBirthMarker(RealNameAuthLog.DOB_MARKER_NOT_VERIFIED);
            dobAgeVerify(birthDate);
            return;
        }

        //是否匹配
        boolean dobEquals = false;
        String[] dobArr = bvnDob.split(",");
        for (int i = 0; i < dobArr.length; i++) {
            if (dobEquals = StringUtils.equals(DateUtils.multipleParse(birthDate, DateUtils.MM_YYYY), DateUtils.multipleParse(dobArr[i], DateUtils.MM_YYYY)))
                break;
        }
        realNameAuthLog.setBirthMarker(dobEquals ? RealNameAuthLog.DOB_MARKER_MATCHED : RealNameAuthLog.DOB_MARKER_NOT_MATCHED);
        //如果不匹配 且 开关打开
        if (!dobEquals && kycApiSwitch(KycApiEnum.BVN_DOB_VERIFY)) {
            throw new BusinessException(RspEnmu.REAL_NAME_CHECK_FAILED_BY_BIRTHDAY);
        }
        //是否超龄 以用户提交为准
        dobAgeVerify(birthDate);
    }

    // 22> 年龄 >55，实名认证失败
    private void dobAgeVerify(String dob) {
        String birth = DateUtils.multipleParse(dob, DateUtils.MM_YYYY);
        int age = DateUtils.getAgeByBirth(birth);
        if (22 > age || age > 55) {
            throw new BusinessException(RspEnmu.REAL_NAME_CHECK_FAILED_OVER_AGE);
        }
    }


    //注册号码与BVN号码是否一致
    private JSONObject verifyIdentity(ExtIdentityResult identityResult) {
        String customerNo = getCustNo();
        //注册手机号与BVN手机号不一致,返回状态码，进入重填BVN手机号页面
        StringBuilder str = new StringBuilder();
        String curPhone = getAppUser().getPhone();
        String matchPhone = this.matchPhone(curPhone, Func.toStrArray(identityResult.getPhones()));
        if (Func.isEmpty(matchPhone)) {
            str.append("30002");
        }
        JSONObject obj = new JSONObject();
        obj.put("unmatch", str);
        redisCache.setCacheObject(BizConstants.REAL_NAME_PHONE + customerNo, addZero(curPhone), 60 * 30, TimeUnit.SECONDS);
        log.info("设置PhoneKey,{}", BizConstants.REAL_NAME_PHONE + customerNo);
        return obj;

    }

    private ExtIdentityResult extApiVerification(BVNInfo bvnInfo) {

        // 查询本地数据
        ExtIdentityResult extIdentityResult = getLocalExtIdentity(bvnInfo.getAccountNumber(), bvnInfo.getBvnAccount());
        if (extIdentityResult != null) {
            kycExqApiThreadLocal.set(extIdentityResult.getApiName());
            return extIdentityResult;
        }
        List<String> bvnInvalidDataApiNames = getBvnInvalidDataApiNames(bvnInfo.getBvnAccount());
        List<String> accountInvalidDataApiNames = getAccountInvalidDataApiNames(bvnInfo.getAccountNumber());

        if (!Func.hasEmpty(bvnInfo.getAccountNumber(),bvnInfo.getBankCode())) {
            extIdentityResult = accountVerification(bvnInfo, accountInvalidDataApiNames);
            if (extIdentityResult != null) {
                return extIdentityResult;
            }
        }
        return bvnVerification(bvnInfo, bvnInvalidDataApiNames);
    }

    /**
     * 根据 bvn 查询失效数据apiName列表
     * @param bvnAccount
     * @return
     */
    private List<String> getBvnInvalidDataApiNames(String bvnAccount){
        if (Func.isEmpty(bvnAccount)) {
            return Collections.emptyList();
        }
        List<ExtIdentityResult> bvnList = extIdentityResultMapper.selectLastApiNamesStateByBvn(bvnAccount);
        // 获取失效数据apiName列表
        return bvnList.stream().filter(item -> BizConstants.BIZ_STATUS_0.equals(item.getState()))
                .map(ExtIdentityResult::getApiName).collect(Collectors.toList());
    }

    /**
     * 根据 account 查询失效数据apiName列表
     * @param accountNumber
     * @return
     */
    private List<String> getAccountInvalidDataApiNames(String accountNumber){
        if (Func.isEmpty(accountNumber)) {
            return Collections.emptyList();
        }
        List<ExtIdentityResult> acountList = extIdentityResultMapper.selectLastApiNamesStateByAccount(accountNumber);
        // 获取失效数据apiName列表
        return acountList.stream().filter(item -> BizConstants.BIZ_STATUS_0.equals(item.getState()))
                .map(ExtIdentityResult::getApiName).collect(Collectors.toList());
    }

    private ExtIdentityResult accountVerification(BVNInfo bvnInfo, List<String> invalidDataApiNames) {

        //前端PayStackCode获取所有银行code
        BankCode bankCode = bankListService.getBankCodeByPayStackCode(bvnInfo.getBankCode());
        if (Func.isEmpty(bankCode)) {
            throw new BusinessException(RspEnmu.NOT_SUPPORT_BANK);
        }
        //先查onepipe  查询1、Lookup-Account-Mid
        ExtIdentityResult extIdentityResult = null;
        if (!invalidDataApiNames.contains("Lookup-Account-Mid")) {
            extIdentityResult = onePipeIdentity(bvnInfo, bankCode, getUserId());
        }

        if (extIdentityResult == null && !invalidDataApiNames.contains("NUBAN-Identity-Verification")) {
            extIdentityResult = getNubanVerifyInfo(bvnInfo, bankCode);
        }
        //实名bvn与用户bvn是否一致
        if (extIdentityResult != null && !extIdentityResult.getBvn().equals(bvnInfo.getBvnAccount())) {
            throw new BusinessException(RspEnmu.REAL_NAME_CHECK_FAILED_BY_BVN_2);
        }
        return extIdentityResult;
    }


    @Override
    public ExtIdentityResult bvnVerification(BVNInfo bvnInfo, List<String> invalidDataApiNames) {

        List<SysDictData> sysDict = sysDictDataService.selectDictLabelByType(KycApiEnum.KYC_API.getType());
        for (SysDictData e : sysDict) {
            if (!StringUtils.equalsAny(e.getDictValue(), KycApiEnum.CRS_FIND_BVN2.getValue(), KycApiEnum.OKRA_BVN_VERIFY.getValue()))
                continue;
            if ((KycApiEnum.CRS_FIND_BVN2.getValue().equals(e.getDictValue()) && invalidDataApiNames.contains("CRS-findByBvn2")) ||
                    (KycApiEnum.OKRA_BVN_VERIFY.getValue().equals(e.getDictValue()) && invalidDataApiNames.contains("Okra-bvn-verify"))) {
                continue;
            }
            String methodName = ReflectUtils.GETTER_PREFIX + e.getDictValue();
            log.info("执行动态方法：{}", methodName);
            Object o = ReflectUtils.invokeMethod(this, methodName, new Class[]{BVNInfo.class}, new Object[]{bvnInfo});
            if (o != null) {
                //校验bvn
                ExtIdentityResult extIdentifyResult = (ExtIdentityResult) o;
                log.info("返回动态方法{}的结果：{}", methodName, extIdentifyResult);
                return extIdentifyResult;
            }
        }
        return null;
    }

    private ExtIdentityResult getLocalExtIdentity(String account, String bvn) {
        QueryWrapper<ExtIdentityResult> queryWrapper = new QueryWrapper<>();
        queryWrapper.and(wrapper -> wrapper.eq("bvn", bvn).eq("state", BizConstants.BIZ_STATUS_1));
        if (Func.isNotEmpty(account)) {
            queryWrapper.or(wrapper -> wrapper.eq("account_number", account).in("api_name", "Lookup-Account-Mid", "NUBAN-Identity-Verification", "NUBAN-Identity-Verification-back").eq("state", BizConstants.BIZ_STATUS_1));
        }
        queryWrapper.last("order by id desc limit 1");
        List<ExtIdentityResult> list = extIdentityResultService.list(queryWrapper);
        return list.size() > 0 ? list.get(0) : null;
    }


    private ExtIdentityResult onePipeIdentity(BVNInfo bvnInfo, BankCode bankCode, String userId) {

        if (!kycApiSwitch(KycApiEnum.ONE_PIPE_LOOKUP_ACCOUNT) || Func.isEmpty(bankCode.getOnepipeBankCode())) {
            return null;
        }
        ExtIdentityResult extIdentityResult = new ExtIdentityResult();
        try {
            this.appendReqExtApi("Lookup-Account-Mid");

            JSONObject data = onePipeService.lookupAccount(bvnInfo.getAccountNumber(), bankCode.getOnepipeBankCode(), userId);
            if (data == null) {
                return null;
            }
            extIdentityResult.setApiName("Lookup-Account-Mid");
            data = JSONObject.parseObject(data.get("data").toString());
            JSONObject response = JSONObject.parseObject(data.get("provider_response").toString());
            extIdentityResult.setAccountNumber(bvnInfo.getAccountNumber());
            extIdentityResult.setBankCode(bvnInfo.getBankCode());
            extIdentityResult.setUserId(userId);
            extIdentityResult.setCustNo(getCustNo());
            extIdentityResult.setBvn(Func.jsonToStr(response.get("bvn")));
            extIdentityResult.setDob(Func.jsonToStr(response.get("dob")));
            extIdentityResult.setGender(Func.jsonToStr(response.get("gender")));
            extIdentityResult.setPhones(Func.jsonToStr(response.get("mobile_number")));
            extIdentityResult.setPhone1(Func.jsonToStr(response.get("mobile_number")));
            extIdentityResult.setFullname(Func.jsonToStr(response.get("account_name")));
            extIdentityResult.setFirstname(Func.jsonToStr(response.get("first_name")));
            extIdentityResult.setMiddlename(Func.jsonToStr(response.get("middle_name")));
            extIdentityResult.setLastname(Func.jsonToStr(response.get("last_name")));
            extIdentityResult.setCurrency(Func.jsonToStr(response.get("account_currency")));
            extIdentityResult.setCustomer(Func.jsonToStr(response.get("customer_id")));

            //Func.hasEmpty(extIdentityResult.getDob(), extIdentityResult.getPhones())
            extIdentityResult.setState(BizConstants.REAL_NAME_LOG_NORMAL);
            if ("N/A".equals(extIdentityResult.getBvn()) || Func.isEmpty(extIdentityResult.getBvn())
                    || Func.isEmpty(extIdentityResult.getPhones())
                    || extIdentityResult.getPhones().length() < 10) {
                extIdentityResult.setState(BizConstants.REAL_NAME_LOG_FAIL);
            }
            extIdentityResultService.insertExtIdentityResult(extIdentityResult);
        } catch (Exception e) {
            log.error("onePipeIdentity error", e);
            this.apiExceptionHandle(KycApiEnum.ONE_PIPE_LOOKUP_ACCOUNT, RspEnmu.EXT_API_EXCEPTION_OKRA_ONE_PIPE, e);
        }

        if (BizConstants.REAL_NAME_LOG_FAIL.equals(extIdentityResult.getState())) {
            return null;
        }
        return extIdentityResult;
    }

    public ExtIdentityResult getNubanVerifyInfo(BVNInfo bvnInfo, BankCode bankCode) {

        if (!kycApiSwitch(KycApiEnum.OKRA_IDENTITY_VERIFICATION) || Func.isEmpty(bankCode.getOkraBankId())) {
            return null;
        }
        JSONObject apiRes;
        String userId = getUserId();
        JSONObject reqData = new JSONObject();
        reqData.put("nuban", bvnInfo.getAccountNumber());
        reqData.put("bank", bankCode.getOkraBankId());
        String apiName = "NUBAN-Identity-Verification";
        this.appendReqExtApi(apiName);

        try {
            apiRes = externalApiService.invokePayStackApi("NUBAN-Identity-Verification", okraConfig.getNubanVerify(), "POST", reqData, userId);
        } catch (Exception e) {
            log.error("Okra 接口异常");
            if (e instanceof BusinessException && RspEnmu.NETWORK_BUSY.getCode().equals(((BusinessException) e).getCode())) {
                //超时调用备用接口
                log.error("Okra 接口异常，尝试更换备用服务器API");
                apiName = "NUBAN-Identity-Verification-back";
                this.appendReqExtApi(apiName);
                try {
                    apiRes = externalApiService.invokePayStackApi("NUBAN-Identity-Verification-back", okraConfig.getBackNubanVerify(), "POST", reqData, userId);
                } catch (Exception exception) {
                    //接口网络异常
                    if (e instanceof BusinessException && RspEnmu.NETWORK_BUSY.getCode().equals(((BusinessException) e).getCode())) {
                        throw new BusinessException(RspEnmu.EXT_API_EXCEPTION_OKRA);
                    }
                    return null;
                    //throw new BusinessException(RspEnmu.REAL_NAME_CHECK_FAILED_BY_OKRA_BACK);
                }
            } else {
                log.error("Okra服务器API异常", e);
                return null;
                //throw new BusinessException(RspEnmu.REAL_NAME_CHECK_FAILED_BY_OKRA);
            }
        }
        return dealIdentityVerifyResult(userId, apiRes, bvnInfo, apiName);
    }

    public ExtIdentityResult dealIdentityVerifyResult(String userId, JSONObject apiRes, BVNInfo bvnInfo, String apiName) {
        ExtIdentityResult extIdentifyResult = getExtIdentityResult(userId, apiRes, bvnInfo.getAccountNumber(), bvnInfo.getBankCode());
        extIdentifyResult.setApiName(apiName);
        //生日或手机号码为空
        if (Func.isEmpty(extIdentifyResult.getPhones())) {
            extIdentifyResult.setState(BizConstants.REAL_NAME_LOG_FAIL);
        }
        //Bvn接口不保存
        if ("Okra-bvn-verify".equals(apiName)) {
            extIdentifyResult.setAccountNumber(null);
        }
        extIdentityResultService.insertExtIdentityResult(extIdentifyResult);
        return extIdentifyResult;
    }

    private ExtIdentityResult getExtIdentityResult(String userId, JSONObject apiRes, String accountNumber, String bankCode) {
        ExtIdentityResult extIdentifyResult = new ExtIdentityResult();
        extIdentifyResult.setUserId(userId);
        extIdentifyResult.setCustNo(getCustNo(userId));
        extIdentifyResult.setAccountNumber(accountNumber);
        extIdentifyResult.setBankCode(bankCode);

        JSONObject identity = apiRes.getJSONObject("identity");
        if (identity == null) {
            identity = apiRes;
        }
        if (identity != null && identity.size() > 0) {
            //逗号转空格
            extIdentifyResult.setFullname(getValue(identity, "fullname"));
            extIdentifyResult.setFirstname(CheckAccountNameUtil.commaConvertSpace(getValue(identity, "firstname")));
            extIdentifyResult.setMiddlename(CheckAccountNameUtil.commaConvertSpace(getValue(identity, "middlename")));
            extIdentifyResult.setLastname(CheckAccountNameUtil.commaConvertSpace(getValue(identity, "lastname")));

            extIdentifyResult.setGender(getValue(identity, "gender"));
            extIdentifyResult.setDob(getValue(identity, "dob"));
            extIdentifyResult.setBvn(getValue(identity, "bvn"));
            extIdentifyResult.setVerifyStatus(getValue(identity, "status"));

            extIdentifyResult.setScore(getValue(identity, "score"));
            extIdentifyResult.setEnv(getValue(identity, "env"));
            extIdentifyResult.setLastUpdated(getValue(identity, "last_updated"));
            extIdentifyResult.setPhotoId(getValue(identity, "photo_id"));

            JSONArray phoneArray = identity.getJSONArray("phone");
            if (Func.isNotEmpty(phoneArray) && phoneArray.size() > 0) {
                //数组
                if (phoneArray.toString().startsWith("[")){
                    extIdentifyResult.setPhone1(phoneArray.getString(0));
                    if (phoneArray.size() > 1) {
                        extIdentifyResult.setPhone2(phoneArray.getString(1));
                    }
                    List<String> phoneList = phoneArray.toJavaList(String.class);
                    String phones = phoneList.stream().collect(Collectors.joining(","));
                    extIdentifyResult.setPhones(phones);
                }else {
                    extIdentifyResult.setPhone1(phoneArray.toString());
                }
            }

            JSONArray emailArray = identity.getJSONArray("email");
            if (Func.isNotEmpty(emailArray) && emailArray.size() > 0) {
                //数组
                if (emailArray.toString().startsWith("[")) {
                    extIdentifyResult.setEmail1(emailArray.getString(0));
                    if (emailArray.size() > 1) {
                        extIdentifyResult.setEmail2(emailArray.getString(1));
                    }
                } else {
                    extIdentifyResult.setEmail1(emailArray.toString());
                }

            }

            extIdentifyResult.setVerified(getValue(identity, "verified"));
            extIdentifyResult.setCreatedAt(getValue(identity, "created_at"));
            extIdentifyResult.setRecord(getValue(apiRes, "record"));
        }

        JSONObject receipt = new JSONObject();
        if (checkReceipt(apiRes)) {
            receipt = apiRes.getJSONObject("receipt").getJSONObject("data").getJSONObject("receipt");
            extIdentifyResult.setOwner(getValue(receipt, "owner"));
            extIdentifyResult.setPlanType(getValue(receipt, "plan_type"));
            extIdentifyResult.setCharge(getValue(receipt, "charge"));
            extIdentifyResult.setMethod(getValue(receipt, "method"));
            extIdentifyResult.setWalletBalance(getValue(receipt, "wallet_balance"));
            extIdentifyResult.setType(getValue(receipt, "type"));
            extIdentifyResult.setBillingstatus(getValue(receipt, "billingStatus"));
            extIdentifyResult.setPlanTerm(getValue(receipt, "plan_term"));
            extIdentifyResult.setArchived(getValue(receipt, "archived"));
            extIdentifyResult.setPaid(getValue(receipt, "paid"));
            extIdentifyResult.setCurrency(getValue(receipt, "currency"));
        }

        extIdentifyResult.setAccount(getValue(apiRes, "account"));
        extIdentifyResult.setCustomer(getValue(apiRes, "customer"));
        return extIdentifyResult;
    }

    public ExtIdentityResult getCRS_FindByBvn2(BVNInfo bvnInfo) {

        if (!kycApiSwitch(KycApiEnum.CRS_FIND_BVN2)) {
            return null;
        }

        String userId = getUserId();
        //查询crs 保存实名信息到extResult表 异步调用征信报告并保存
        try {
            this.appendReqExtApi("CRS_FindByBvn2");
            ExtIdentityResult extIdentifyResult = crsService.findByBvn2(userId, bvnInfo.getBvnAccount());
            extIdentifyResult.setUserId(userId);
            extIdentifyResult.setCustNo(getCustNo(extIdentifyResult.getUserId()));
            extIdentifyResult.setBvn(bvnInfo.getBvnAccount());
            extIdentifyResult.setBankCode(bvnInfo.getBankCode());
            //手机号码为空
            if (Func.isEmpty(extIdentifyResult.getPhones())) {
                extIdentifyResult.setState(BizConstants.REAL_NAME_LOG_FAIL);
            }
            extIdentityResultService.insertExtIdentityResult(extIdentifyResult);

            //异步保存报告
            crsService.createCrsReportAsync(extIdentifyResult.getCrsRegistryId(), userId, extIdentifyResult.getBvn());
            return extIdentifyResult;
        } catch (Exception e) {
            log.error("实名认证查询Crs信息异常, bvnInfo:{}", bvnInfo.toString(), e);
            //接口异常
            this.apiExceptionHandle(KycApiEnum.CRS_FIND_BVN2, RspEnmu.EXT_API_EXCEPTION_CRS_BVN2, e);
            return null;
        }
    }


    public ExtIdentityResult getOkra_bvn_verify(BVNInfo bvnInfo) {

        if (!kycApiSwitch(KycApiEnum.OKRA_BVN_VERIFY)) {
            return null;
        }
        JSONObject reqBvn = new JSONObject();
        reqBvn.put("currency", "NGN");
        reqBvn.put("bvn", bvnInfo.getBvnAccount());
        try {
            String apiName = "Okra-bvn-verify";
            this.appendReqExtApi(apiName);
            JSONObject apiRes = externalApiService.invokePayStackApi(apiName, okraConfig.getBvnVerify(), "POST", reqBvn, getUserId());
            ExtIdentityResult extIdentityResult = dealIdentityVerifyResult(getUserId(), apiRes, bvnInfo, apiName);
            return extIdentityResult;
        } catch (Exception e) {
            if(e instanceof BusinessException) {
                log.error("Okra-Bvn 接口异常：{}", ((BusinessException) e).getDetails(), e);
            } else {
                log.error("Okra-Bvn 接口异常，", e);
            }

            this.apiExceptionHandle(KycApiEnum.OKRA_BVN_VERIFY, RspEnmu.EXT_API_EXCEPTION_OKRA_BVN, e);
            return null;
        }
    }

    private boolean checkReceipt(JSONObject apiRes) {
        if (apiRes.get("receipt") == null || apiRes.getJSONObject("receipt").size() <= 0) {
            return false;
        }

        if (apiRes.getJSONObject("receipt").get("data") == null || apiRes.getJSONObject("receipt").getJSONObject("data").size() <= 0) {
            return false;
        }

        if (apiRes.getJSONObject("receipt").getJSONObject("data").get("receipt") == null || apiRes.getJSONObject("receipt").getJSONObject("data").getJSONObject("receipt").size() <= 0) {
            return false;
        }

        return true;
    }


    /**
     * 接口开关
     *
     * @param apiEnum
     * @return
     */
    public boolean kycApiSwitch(KycApiEnum... apiEnum) {
        String[] values = new String[apiEnum.length];
        for (int i = 0; i < apiEnum.length; i++) {
            values[i] = apiEnum[i].getValue();
        }
        List<String> labels = sysDictDataService.selectDictLabel(apiEnum[0].getType(), values);
        return labels.size() > 0 && labels.contains("1");
    }

    /**
     * 接口开关Mock
     *
     * @param
     * @return
     */
    protected boolean kycApiSwitchMock(String value) {
        return "1".equals(sysDictDataService.selectDictLabel("mock_switch", value));
    }


    private void appendReqExtApi(String apiName) {
        String api = kycExqApiThreadLocal.get();
        if (Func.isNotEmpty(api)) {
            kycExqApiThreadLocal.set(api + "," + apiName);
            return;
        }
        kycExqApiThreadLocal.set(apiName);
    }

    /**
     * 实名接口统一处理
     *
     * @return
     */
    private boolean apiExceptionHandle(KycApiEnum apiEnum, RspEnmu timeOutEnmu, Exception e) {
        //接口网络异常
        if (e instanceof BusinessException && RspEnmu.NETWORK_BUSY.getCode().equals(((BusinessException) e).getCode())) {
            //没有后续扭转接口时抛出接口异常重试
            if (businessMapper.dictValueFollowUp(apiEnum.getValue()) == 0) {
                throw new BusinessException(timeOutEnmu);
            }
        }
        return true;
    }

    public int realNameFailPreCheck(String userId) {
        RealNameAuthLog realNameAuthLog = new RealNameAuthLog();
        realNameAuthLog.setUserId(userId);
        realNameAuthLog.setState(BizConstants.REAL_NAME_LOG_FAIL);
        List<RealNameAuthLog> realNameAuthLogs = realNameAuthLogService.selectRealNameAuthLogList(realNameAuthLog);
        if (realNameAuthLogs.size() > 2) {
            throw new BusinessException("Sorry, your Identity verification failed.", RspEnmu.REAL_NAME_CHECK_FAILED_JUMP_PAGE.getCode());
        }
        //失败次数1 7天内限制实名
        if (realNameAuthLogs.size() == 1 && Func.dayDifference(realNameAuthLogs.get(0).getCreateTime()) < 7) {
            throw new BusinessException("You can go to the bank to update your information and try again in " + (new BigDecimal(7).subtract(new BigDecimal(Func.dayDifference(realNameAuthLogs.get(0).getCreateTime()))).setScale(0, BigDecimal.ROUND_UP)) + " days.", RspEnmu.REAL_NAME_CHECK_FAILED_JUMP_PAGE.getCode());
        }
        //失败次数2 30天内限制实名
        if (realNameAuthLogs.size() == 2 && Func.dayDifference(realNameAuthLogs.get(0).getCreateTime()) < 30) {
            throw new BusinessException("You can go to the bank to update your information and try again in " + (new BigDecimal(30).subtract(new BigDecimal(Func.dayDifference(realNameAuthLogs.get(0).getCreateTime()))).setScale(0, BigDecimal.ROUND_UP)) + " days.", RspEnmu.REAL_NAME_CHECK_FAILED_JUMP_PAGE.getCode());
        }
        return realNameAuthLogs.size();
    }

    @Override
    public boolean accountApiCheck() {
        return kycApiSwitch(KycApiEnum.ONE_PIPE_LOOKUP_ACCOUNT, KycApiEnum.OKRA_IDENTITY_VERIFICATION);
    }

    //校验注册手机是否一致
    public JSONObject verifyPhone(String phone, String accountNumber, String bvn) {

        String sourcePhone = phone;
        if (Func.isEmpty(bvn)) {
            RealNameAuthLog realNameAuthLogQ = new RealNameAuthLog();
            realNameAuthLogQ.setUserId(getUserId());
            realNameAuthLogQ.setAccountNumber(accountNumber);
            List<RealNameAuthLog> realNameAuthLogs = realNameAuthLogService.selectRealNameAuthLogList(realNameAuthLogQ);
            if (realNameAuthLogs.size() == 0) {
                throw new BusinessException(RspEnmu.NO_RECORD);
            }
            bvn = realNameAuthLogs.get(0).getBvnAccount();
        }

        ExtIdentityResult extIdentityResultQ = new ExtIdentityResult();
        extIdentityResultQ.setBvn(bvn);
        extIdentityResultQ.setState(BizConstants.REAL_NAME_LOG_NORMAL);
        List<ExtIdentityResult> extIdentityResults = extIdentityResultService.selectExtIdentityResultList(extIdentityResultQ);
        if (extIdentityResults.size() == 0) {
            throw new BusinessException(RspEnmu.NO_RECORD);
        }
        ExtIdentityResult extIdentity = extIdentityResults.get(0);

        StringBuilder str = new StringBuilder();
        String custNo = getCustNo();
        //匹配手机号 默认第一个号码 或 手机号码
        String matchPhone = matchPhone(phone, Func.toStrArray(extIdentity.getPhones()));
        //返回null：匹配失败
        if (Func.isEmpty(matchPhone)) {
            str.append("30002");
            phone = extIdentity.getPhone1();
            businessMapper.appendErrorMsgByCustNo(custNo, RspEnmu.REAL_NAME_CHECK_FAILED_AGAIN.getMessage());
        } else {
            phone = matchPhone;
            log.info("设置PhoneKey，{}", BizConstants.REAL_NAME_PHONE + custNo);
            redisCache.setCacheObject(BizConstants.REAL_NAME_PHONE + custNo, addZero(phone), 60 * 30, TimeUnit.SECONDS);
        }
        //脱敏字段
        JSONObject obj = new JSONObject();
        obj.put("unmatch", str);
        obj.put("phone", DesensitizeUtils.desensitize(phone, 3, 4));

        if (Func.isNotEmpty(str)) {
            asyncService.apdUserTag(AppUserMark.builder().userId(getUserId()).build(), UserTagEnum.IDENTITY_VERIFIED_FAILED_BVN_PHONE);
        }
        //追加实名日志phoneNumber
        realNameAuthLogService.appendPhones(getCustNo(), sourcePhone);
        return obj;
    }

    private String matchPhone(String curPhone, String... phones) {
        //校验后10位号码
        curPhone = StringUtils.substringAfter(curPhone, 10);
        String res = null;
        for (int i = 0; i < phones.length; i++) {
            String phone = StringUtils.substringAfter(phones[i], 10);
            if (curPhone.equals(phone)) {
                res = curPhone;
                break;
            }
        }
        return res;
    }

    private String getValue(JSONObject json, String fieldName) {
        return json.get(fieldName) == null ? "" : json.getString(fieldName);
    }

    private String addZero(String phone) {
        if (phone.length() == 11) {
            return phone;
        }
        return "0" + phone;
    }


}
